
from stream import models, dbrequests

BEST_TAGS_COUNT = 5 # Number of tags to store for recommended users
BEST_USERS_COUNT = 5 # Number of recommended users to retrieve

def searchUsers(viewer_id, tags = None):
    import math

    def tagNorm(tag_list):
        norm = math.sqrt(reduce(lambda x, y: x + y[1]*y[1], tag_list, 0))
        return norm

    def makeTags(user_id, published_only):
        """
        Return (<tag vector>, <norm of tag vector>)
        """
        tag_list = dbrequests.getUserTags(user_id, published_only)
        norm = tagNorm(tag_list)
        return (tag_list, norm)

    def getCosine(tags1, norm1, tags2, norm2):
        """
        Scalar product of two tag vectors and a multiple of encounters of each tag in first and second vector.
        norm1 and norm2 should not be zero.
        """
        # Rely on tags1 and tags2 being sorted by tag name
        scalar = 0
        tag_contrib = []
        try:
            iter1 = tags1.__iter__()
            iter2 = tags2.__iter__()
            val1 = iter1.next()
            val2 = iter2.next()
            while True:
                if val1[0] < val2[0]:
                    val1 = iter1.next()
                elif val2[0] < val1[0]:
                    val2 = iter2.next()
                else:
                    scalar += val1[1] * val2[1]
                    tag_contrib.append((val1[0], val1[1] * val2[1]))
                    val1 = iter1.next()
                    val2 = iter2.next()
        except StopIteration:
            pass
        # Take only BEST_TAGS_COUNT most encountered tags:
        tag_contrib = sorted(tag_contrib, lambda x, y: y[1] - x[1])[:BEST_TAGS_COUNT]
        tag_contrib = map(lambda x: x[0], tag_contrib)
        return (scalar / (norm1 * norm2), tag_contrib)

    if tags is None:
        viewer_tags = makeTags(viewer_id, False)
    else:
        viewer_tags = map(lambda x: (x, 1), tags)
        viewer_tags = (viewer_tags, math.sqrt(len(tags)))
    user_tags = []
    for user in models.User.objects.all():
        if user.id != viewer_id:
            tags = makeTags(user.id, True)
            if tags[1] == 0 or viewer_tags[1] == 0:
                continue
            user_tags.append((user, getCosine(tags[0], tags[1], viewer_tags[0], viewer_tags[1])))
    def compareFloat(x, y):
        if x > y:
            return -1
        elif x < y:
            return 1
        else:
            return 0
    user_tags = sorted(user_tags, cmp = compareFloat, key = lambda x: x[1][0])
    user_tags = user_tags[:BEST_USERS_COUNT]
    return map(lambda x: (x[0], x[1][1]), user_tags)
